﻿using System;
using System.Collections.Generic;
using FunctionalCLR.Core;
using FunctionalCLR.Core.Function.Arithmetics;
using NUnit.Framework;
using Zove.Compiler;
using Zove.Compiler.EquationTransformer;
using Zove.Runtime;
using Zove.Runtime.FunctionalCLR;
using Zove.Runtime.InferencingStrategy;

namespace Zove.Test {
    [TestFixture]
    public class RuntimeTests {
        public static void ShowSolution(Instance sln) {
            foreach (RuntimeFactor rf in sln.Model.Factors) {
                SolutionSimplifier ss = new SolutionSimplifier(rf);
                string expr = ss.Parse();
                Console.WriteLine(string.Format(
                    "{0} = {1}:{2}",
                    rf.Name, rf.Value, expr));
                //sln.Queue.ContainsKey(e.Name)
                //? sln.Queue[e.Name].Rule.Root.ToString() : "input")));
            }
        }

        [Test]
        public void TestConstant()
        {
            Dictionary<string, double> table
                = new Dictionary<string,double>();
            table.Add("r", 2);

            Instance instance = TestModel(
                "SphereCone", table);
            instance.Evaluate();
        }

        [Test]
        public void TestRuntimeModelLoader() {
            DefaultClassFinder finder = new DefaultClassFinder();
            DefaultClassLoader classLoad = new DefaultClassLoader();
            classLoad.Finder = finder;
            DefaultRuntimeModelLoader matLoad = new DefaultRuntimeModelLoader();
            matLoad.ClassLoader = classLoad;
            RuntimeModel rm = matLoad.LoadByClassName("circle");

            Console.WriteLine(rm.ToString());
        }

        [Test]
        public void TestClassLoad() {
            DefaultClassFinder finder = new DefaultClassFinder();
            DefaultClassLoader load = new DefaultClassLoader();
            load.Finder = finder;
            ZoomClass c = load.Load("cube");

            Console.WriteLine(c.ToString());

            Assert.AreEqual(3, c.Variables.Count);
            Assert.AreEqual(2, c.Equations.Count);
            ZoomEquation e = c.Equations[0];
            Assert.AreEqual(2, e.Elements.Count);
        }

        [Test]
        public void TestBasic() {
            Dictionary<string, double> i = new Dictionary<string, double>();
            i["area"] = 2;
            Instance sln = TestModel("circle", i);
        }

        public static Instance TestModel(
            string p, Dictionary<string, double> initSet) {
            Instance sln = new Instance(p);

            ZoveDependencyManager.Instance.FunctionManager = new ZoveFunctionManager();
            IClassFinder finder = ZoveDependencyManager.Instance.ClassFinder;
            IClassLoader cl = ZoveDependencyManager.Instance.ClassLoader;

            finder.AddWorkspace(@"C:\Documents and Settings\samh\My Documents\Visual Studio 2008\Projects\Zove\Zove.Test\Files");
            DefaultRuntimeModelLoader rml = new DefaultRuntimeModelLoader();
            rml.ClassLoader = cl;
            //rml.Logger = new DefaultLogger();
            sln.Model = rml.LoadByClassName(p);

            foreach (string m in initSet.Keys) {
                RuntimeFactor rf = sln.Model.GetFactorByName(m);
                if (rf == null) {
                    throw new FactorNotDeclaredException(
                        m, "", sln.Model.Name);
                }

                rf.Status = RuntimeFactorStatus.Input;
                rf.Value = initSet[m];
            }
            sln.Evaluate();

            ShowSolution(sln);
            //DisplayUnsolvedFactors(sln);

            return sln;
        }

        private static void DisplayUnsolvedFactors(Instance sln) {
            foreach (RuntimeFactor rf in sln.Model.Factors) {
                if (rf.Status == RuntimeFactorStatus.Unknown) {
                    Console.WriteLine(
                        string.Format("{0}={1}:{2}", rf.Name,
                        rf.Value.ToString(),
                        (rf.Status == RuntimeFactorStatus.Input ? "<input>" :
                        (rf.Status == RuntimeFactorStatus.Solved ? "<solved>" : "<unknown>")
                        ))
                        );
                }
            }
        }

        [Test]
        public void TestInstance() {
            Dictionary<string, double> i 
                = new Dictionary<string, double>();
            i["c.area"] = 2;
            Instance sln = TestModel("sphere", i);
        }

        [Test]
        public void TestInheritance() {
            Dictionary<string, double> i 
                = new Dictionary<string, double>();
            i["edge"] = 2;

            Instance sln = TestModel("square", i);
            Assert.AreEqual(4,
                sln.Model.GetFactorByName("area").Value);
        }

        [Test]
        public void TestParse() {
            Parser p = new Parser("a - b*c");
            p.ConstantManager
                = DefaultDependencyManager.Instance.ConstantManager;
            p.FunctionManager
                = DefaultDependencyManager.Instance.FunctionManager;
            Node root = p.Parse();
            root.PrintTree();
            DefaultExpressionCompiler eci = new DefaultExpressionCompiler();
            Console.WriteLine(eci.Compile(root));
        }

        [Test]
        public void TestEmbrace() {
            Parser p = new Parser("abs(1-3)");
            p.ConstantManager
                = DefaultDependencyManager.Instance.ConstantManager;
            p.FunctionManager
                = DefaultDependencyManager.Instance.FunctionManager;
            Node root = p.Parse();
            root.PrintTree();
            DefaultExpressionCompiler eci = new DefaultExpressionCompiler();
            Console.WriteLine(eci.Compile(root));

            string result = root.Evaluate().ToString();
            Console.WriteLine(result);
        }

        [Test]
        public void TestPrintTree() {
            Node root = GetSampleTree();

            root.PrintTree();
        }

        public static Node GetSampleTree() {
            AddNode root = new AddNode();

            FactorNode left = new FactorNode();
            left.Factor = new RuntimeFactor();
            left.Factor.Name = "a";
            left.Factor.Value = 5;
            left.Value = "a";

            FactorNode right = new FactorNode();
            right.Value = "b";
            right.Factor = new RuntimeFactor();
            right.Factor.Value = 6;
            right.Factor.Name = "c";

            root.Left = left;
            root.Right = right;

            return root;
        }

        [Test]
        public void TestRouteFinder() {
            Parser p = new Parser("2*r^2");
            p.ConstantManager = DefaultDependencyManager.Instance.ConstantManager;
            p.FunctionManager = DefaultDependencyManager.Instance.FunctionManager;
            Node root = p.Parse();

            RouteFinder finder = new RouteFinder("r");
            bool result = finder.Visit(root);

            Assert.AreEqual(true, result);
            Assert.AreEqual(1, finder.Route.Pop());
            Assert.AreEqual(0, finder.Route.Pop());
        }

        [Test]
        public void TestCompileExpr() {
            Parser p = new Parser("sin(a+b)");
            p.ConstantManager 
                = DefaultDependencyManager.Instance.ConstantManager;
            p.FunctionManager 
                = DefaultDependencyManager.Instance.FunctionManager;
            Node root = p.Parse();

            DefaultExpressionCompiler impl 
                = new DefaultExpressionCompiler();
            Assert.AreEqual("sin(a+b)", impl.Compile(root));
        }

        [Test]
        public void TestVisit() {
            RuntimeModel m = GetRuntimeModel();

            Node node = GetSampleTree();
            double result = Convert.ToDouble(node.Evaluate());
            Assert.AreEqual(11, result);
        }

        [Test]
        public void TestEvaluate() {
            RuntimeModel m = GetRuntimeModel();

            ZoomParser p = new ZoomParser("((b/3.14)^0.5)", m);
            p.ConstantManager = DefaultDependencyManager.Instance.ConstantManager;
            p.FunctionManager = DefaultDependencyManager.Instance.FunctionManager;
            Node root = p.Parse();
            double result = Convert.ToDouble(root.Evaluate());

            Assert.AreEqual(Math.Round(1.3823270327522743, 5), Math.Round(result, 5));
        }

        [Test]
        public void TestFunction() {
            Parser parser = new Parser("a+sin(b)");
            parser.ConstantManager = DefaultDependencyManager.Instance.ConstantManager;
            parser.FunctionManager = DefaultDependencyManager.Instance.FunctionManager;
            Node root = parser.Parse();

            root.PrintTree();
        }

        [Test]
        public void TestCheckDuplicated() {
            string equ = "b = (a^2+c^2-2*a*c*cos(B))'2";
            List<string> dup = DefaultEquationTransformer.CheckDuplicated(equ);

            Assert.AreEqual(2, dup.Count);
            Assert.AreEqual("a", dup[0]);
            Assert.AreEqual("c", dup[1]);
        }

        [Test]
        public void TestTransformAll() {
            string equ = "b = (a^2+c^2-2*a*c*cos(B))'2";
            RuntimeModel rm = GetRuntimeModel("a", "b", "c", "B");

            DefaultEquationTransformer eti = null;
            //    = new EquationTransformerImpl(equ, rm);
            Dictionary<string, string> e = null; // eti.Transform();

            //Assert.AreEqual(2, e.Count);

            equ = "a+b=c-d";
            rm = GetRuntimeModel("a", "b", "c", "d");
            eti = new DefaultEquationTransformer(equ, rm);
            e = eti.Transform();

            Assert.AreEqual(4, e.Count);
            Assert.AreEqual("((c-d)-a)", e["b"]);
            Assert.AreEqual("((c-d)-b)", e["a"]);
        }

        [Test]
        public void TestGetInstance() {
            object instance = Utils.GetObject(
                @"C:\windows\assembly\System.dll", "System.Byte");

            Assert.IsNotNull(instance);
            Assert.IsTrue(instance is Byte);
        }

        [Test]
        public void TestFind() {
            DefaultClassFinder finder = new DefaultClassFinder();
            Assert.AreEqual(
                finder.GetLocation("cone"),
                finder.GetLocation("cone"));
        }

        [Test]
        public void TestAutomatedModelingBuilding() {
        }

        [Test]
        public void TestTypeChecking() {
            Dictionary<string, double> i = new Dictionary<string, double>();
            i["a"] = 2;

            try {
                Instance sln = TestModel("TypeCheckingTest", i);
                Assert.IsTrue(false, "element undelcared was not checked.");
            } catch (FactorNotDeclaredException ex) {
                Console.WriteLine(ex.Message);
                Assert.IsTrue(true);
            }
        }

        [Test]
        public void TestFill() {
            RuntimeModel rm = GetRuntimeModel();

            string r = string.Empty;
            CSharpExpressionFiller ef = null;

            //ef = new ExpressionFiller("a+b", rm);
            //string r = ef.Fill();
            //Assert.AreEqual("(x+6)", r);
            FactorNode a = new FactorNode();
            a.Value = "a";
            a.Factor = new RuntimeFactor();
            a.Factor.Name = "a";
            a.Factor.Status = RuntimeFactorStatus.Unknown;

            FactorNode b = new FactorNode();
            b.Value = "b";
            b.Factor = new RuntimeFactor();
            b.Factor.Name = "b";
            b.Factor.Value = 6;
            b.Factor.Status = RuntimeFactorStatus.Solved;

            SinNode s = new SinNode();
            s.Parameters.Add(a);
            s.Parameters.Add(b);

            ef = new CSharpExpressionFiller(s);
            r = ef.Fill();

            Assert.AreEqual("Math.Sin(x,6)", r);
        }

        public static RuntimeModel GetRuntimeModel() {
            RuntimeModel rm = new RuntimeModel();
            RuntimeFactor rf = new RuntimeFactor();
            rf.Name = "a";
            rf.Status = RuntimeFactorStatus.Unknown;
            rm.Factors.Add(rf);

            rf = new RuntimeFactor();
            rf.Name = "b";
            rf.Status = RuntimeFactorStatus.Input;
            rf.Value = 6;
            rm.Factors.Add(rf);

            rf = new RuntimeFactor();
            rf.Name = "c";
            rf.Status = RuntimeFactorStatus.Input;
            rf.Value = 6;
            rm.Factors.Add(rf);

            rf = new RuntimeFactor();
            rf.Name = "B";
            rf.Status = RuntimeFactorStatus.Input;
            rf.Value = 6;
            rm.Factors.Add(rf);

            return rm;
        }

        [Test]
        public void TestFindRoot() {
            EquationInferencingStrategy eis 
                = new EquationInferencingStrategy();
            double result = eis.Evaluate("x-2");

            Console.WriteLine(result);
        }
        
        public static RuntimeModel GetRuntimeModel(
            params string[] factors) {
            RuntimeModel rm = new RuntimeModel();

            foreach (string factor in factors) {
                RuntimeFactor rf = new RuntimeFactor();
                rf.Name = factor;
                rf.Status = RuntimeFactorStatus.Unknown;
                rm.Factors.Add(rf);
            }

            return rm;
        }

        [Test]
        public void TestGetFunctionName() {
            Assert.AreEqual(
                "Math.Sin", 
                CSharpExpressionFiller.GetFunctionName("sin"));
        }

        [Test]
        public void TestMulitpleFormulas() {
            Dictionary<string, double> i = new Dictionary<string, double>();
            i["a"] = 1;
            i["c"] = 2;
            i["d"] = 5;

            TestModel("TestMulitpleFormulas", i);
        }

        [Test]
        public void TestNonlinearEquationFinding() {
            Dictionary<string, double> i 
                = new Dictionary<string, double>();
            i["a"] = 1;
            i["A"] = Math.PI / 2;

            TestModel("IsosTriangle", i);
        }

        [Test]
        public void TestComment() {
            Dictionary<string, double> i = new Dictionary<string, double>();

            TestModel("CommentTest", i);
        }

        [Test]
        public void TestSimplify() {
            Dictionary<string, double> i
                = new Dictionary<string, double>();
            i["c.area"] = 2;

            Instance sln = TestModel("sphere", i);

            RuntimeFactor rf = sln.Model.GetFactorByName("volume");

            SolutionSimplifier ss = new SolutionSimplifier(rf);
            string result = ss.Parse();

            Console.WriteLine("result: {0}", result);
        }

        [Test]
        public void TestFunctionDecl() {
            Dictionary<string, double> i = new Dictionary<string, double>();
            i["a"] = 2;

            TestModel("FuncTest", i);
        }

        [Test]
        public void TestFuncComposite() {
            //ZoomFunction func = new ZoomFunction();
            //func.Name = "f(x)";
            //func.Body = "x/3";
            //func.Description = "f(x)";
            //func.LineNumber = 1;

            //RuntimeModel circleModel = ZoveDependencyManager.Instance.RuntimeModelLoader.Load("circle");
            //((ZoveFunctionManager)
            //    ZoveDependencyManager.Instance.FunctionManager
            //    ).Add(
            //    func, circleModel);

            //string expr = "f(a)-3";
            //UserDefinedFunctionConverter converter 
            //    = new UserDefinedFunctionConverter();
            //Parser rp = ZoveDependencyManager.Instance.ParserFactory.CreateParser(expr);
            //Node rr = rp.Parse();
            //string rexpr = converter.Convert(rr);

            //Console.WriteLine(rexpr);
        }

        [Test]
        public void TestFunction2() {
            Dictionary<string, double> i = new Dictionary<string, double>();
            i["a"] = 2;

            TestModel("FuncTest", i);
        }

        [Test]
        public void TestPreprocess() {
            ZoomClass zc = ZoveDependencyManager.Instance.ClassLoader.Load("triangle");
            RuntimeModel rm = new RuntimeModel();
            rm.Name = "Triangle";
            string expr = "Triangle.sine(b.b,b.A,b.B)";
            UserDefinedFunctionConverter converter
                = new UserDefinedFunctionConverter();

            string newe = DefaultRuntimeModelLoader.PreprocessExpr(
                zc, rm, converter, expr);
        }

        [Test]
        public void TestRule() {
            ZoomClass c = ZoveDependencyManager.Instance.ClassLoader.LoadFromSource(
                @"class A {
                    facotr a, b;

                    g(x)->x/3
                    a = g(b);
                }");

            Console.WriteLine(c.ToString());
        }

        [Test]
        public void TestRadianClass() {
            Instance sln = new Instance("RadianClass");

            ZoveDependencyManager.Instance.FunctionManager = new ZoveFunctionManager();
            IClassFinder finder = ZoveDependencyManager.Instance.ClassFinder;
            IClassLoader cl = ZoveDependencyManager.Instance.ClassLoader;

            DefaultRuntimeModelLoader rml = new DefaultRuntimeModelLoader();
            rml.ClassLoader = cl;
            //rml.Logger = new DefaultLogger();
            sln.Model = rml.LoadByClassName("RadianClass");

            sln.Assign("angle", "30 \\angle");
            sln.Evaluate();

            ShowSolution(sln);
        }

        [Test]
        public void TestImportWorkspace() {
            Dictionary<string, double> setup = new Dictionary<string,double>();
            setup.Add("hello.a", 5);

            Instance context = TestModel("ImportTest", setup);
        }
    }
}
